package com.griddroid.serverdaemons.threadhandlers;

import java.io.IOException;
import java.net.Socket;
import java.util.Vector;
import com.griddroid.configuration.GridClientConfig;
import com.griddroid.messages.EndSessionRequestMessage;
import com.griddroid.messages.PingAckMessage;
import com.griddroid.nodes.GridServer;
import com.griddroid.serverdaemons.ClientDaemon;
import com.griddroid.serverdaemons.MergerDaemon;
import com.griddroid.works.WorkRequest;
import com.griddroid.works.WorkResponse;

/**
 * Is responsible for the communication between the server and a client.
 * <p>
 * It is a part of the server, that keeps alive the communication between the
 * server and a client. It is also responsible for sending the ping message
 * every a specified time interval, for checking if new messages have arrived
 * and for deleting a non-responsive client
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 * 
 */
public class ClientHandler extends GenericHandler
{
	private Vector<WorkResponse> finalizedWorks;

	/**
	 * Constructs the client handler, given the socket and the server
	 * 
	 * @param clientSocket
	 *            the socket with which the client tried to connect to the
	 *            server. The client handler will establish a new one.
	 * @param server
	 *            the parent of the client handler. This will make the use of
	 *            shared collections easier
	 * 
	 */
	public ClientHandler(Socket clientSocket, GridServer server)
	{
		super();
		this.server = server;
		this.server.log("Info", "Creating new client handler");
		this.setId(this.server.getClientDaemon().getAvailableId());
		this.firstSocket = clientSocket;
		this.finalizedWorks = new Vector<WorkResponse>();
	}

	/**
	 * The actual functionality of the client handler. It is a continuous loop
	 * that goes through the following steps:
	 * <ol>
	 * <li>gets the socket through which the client tried to connect to the
	 * server. It will be used to send back the new port, on which the permanent
	 * connection will be establish</li>
	 * <li>checks if there are any ready work responses from the
	 * {@link MergerDaemon Merger} and sends them back to the client</li>
	 * <li>checks if the client sent a ping acknowledgment message</li>
	 * <li>checks if the client send an end-session request, it calls
	 * {@link #close() close()} and deletes itself from the client daemon using
	 * {@link ClientDaemon#deleteClientHanlder(String)
	 * deleteClientHanlder(String)}</li>
	 * <li>checks if the client sent a work request. It sets its ID and adds it
	 * to the {@link GridServer server}'s pool</li>
	 * <li>checks if the client did not reply to a ping for a long time period,
	 * so it breaks the connection and deletes itself</li>
	 * </ol>
	 */
	public void run()
	{
		this.acceptConnection();
		this.server.log("Info", "Handler started running");
		while (true)
		{
			if (this.forceExit)
				return;
			checkCounter++;
			// SEND PING to the client
			this.sendPings();

			// check stream to see if there is a new message available and
			// handle it
			try
			{
				while (this.permanentSocket.getInputStream().available() > 0)
				{

					try
					{
						Object temp = this.receive(this.permanentSocket);
						if (temp instanceof WorkResponse)
						{
							this.send(this.permanentSocket, temp);
						}
						else if (temp instanceof PingAckMessage)
						{
							this.acceptPingAckMessage(temp);
						}
						else if (temp instanceof EndSessionRequestMessage)
						{
							this.acceptEndSessionMessage(temp);
							this.close();
							this.server.getClientDaemon().deleteClientHanlder(this.getId());
							return;
						}
						else if (temp instanceof WorkRequest)
						{
							WorkRequest wr = (WorkRequest) temp;
							if (wr.getWorkRequestId().equals(this.getId()))
							{
								this.server.log("Info", "Received work request from client with id " + this.getId());
								if (!this.server.getWorkRequests().add(wr))
									this.server.log("Error", "Could not add work request to the queue");

							}
							else
							{
								this.server.log("Error", "Client hanlder with id " + this.getId() + " received unknown session end message");
							}
						}
						else
						{
							this.server.log("Error", "Client hanlder with id " + this.getId() + " received unknown message");
						}

					}
					catch (IOException e1)
					{
						this.server.log("Error", "Client with id " + this.getId() + " could not receive a message");
					}
					catch (ClassNotFoundException e1)
					{
						this.server.log("Error", "Client with id " + this.getId() + " could not receive a message");
					}
				}
			}
			catch (IOException e1)
			{
				this.server.log("Error", "ClientHandler with id " + this.getId() + " could not check for messages");
			}

			// /send available work responses
			while (!this.finalizedWorks.isEmpty())
			{
				WorkResponse wr = this.finalizedWorks.remove(this.finalizedWorks.size() - 1);
				try
				{
					this.send(this.permanentSocket, wr);
					this.server.log("Info", "Just sent a work response");
				}
				catch (IOException e)
				{
					this.server.log("Error", "Could not send responce message an clinet with id " + this.getId());
				}
			}

			// Check for timeouts
			if (!this.checkPingsTimeouts())
			{
				this.server.getClientDaemon().deleteClientHanlder(this.getId());
			}

			try
			{
				Thread.sleep(GridClientConfig.CHECKINTERVAL);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * 
	 * @return a vector containing the responses that are ready to be sent to
	 *         the client
	 */
	public Vector<WorkResponse> getFinalizedWorks()
	{
		return finalizedWorks;
	}

	/**
	 * Before closing, the client handler closes the socket and releases the
	 * port. Thus it cleans all of its traces on the server. Finally it writes
	 * that event to the log
	 */
	@Override
	public void close()
	{
		if (this.permanentSocket != null)
		{
			try
			{
				this.handlerSocket.close();
				this.permanentSocket.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
		this.server.freePort(this.currentPort);
		this.server.log("Error", "Exiting hanlder with id " + this.getId());
		// this.server.getClientDaemon().deleteClientHanlder(this.getId());
		this.forceExit = true;

	}
}